from torch.nn import CrossEntropyLoss
from transformers import AutoTokenizer, T5PreTrainedModel, T5Config, T5EncoderModel
from transformers.modeling_outputs import TokenClassifierOutput
from typing import Union, Optional, Tuple
from transformers import pipeline
import torch

class T5EncoderForTokenClassification(T5PreTrainedModel):
    _tied_weights_keys = ["encoder.embed_tokens.weight"]

    def __init__(self, config: T5Config):
        super().__init__(config)
        self.transformer = T5EncoderModel(config)
        self.classification_head = torch.nn.Linear(config.hidden_size, config.num_labels)

        # Initialize weights and apply final processing
        self.post_init()

        self.model_parallel = False

    def forward(
            self,
            input_ids: Optional[torch.LongTensor] = None,
            attention_mask: Optional[torch.FloatTensor] = None,
            head_mask: Optional[torch.FloatTensor] = None,
            inputs_embeds: Optional[torch.FloatTensor] = None,
            labels: Optional[torch.LongTensor] = None,
            output_attentions: Optional[bool] = None,
            output_hidden_states: Optional[bool] = None,
            return_dict: Optional[bool] = None,
    ) -> Union[Tuple, TokenClassifierOutput]:
        # r"""
        # Returns:
        #
        # Example:
        #
        # ```python
        # >>> from transformers import AutoTokenizer, MTxEncoderForTokenClassification
        #
        # >>> tokenizer = AutoTokenizer.from_pretrained("MTx-small")
        # >>> model = MTxEncoderForTokenClassification.from_pretrained("MTx-small")
        # >>> input_ids = tokenizer(
        # ...     "Studies have been shown that owning a dog is good for you", return_tensors="pt"
        # ... ).input_ids  # Batch size 1
        # >>> outputs = model(input_ids=input_ids)
        # >>> logits = outputs.logits
        # ```"""
        return_dict = return_dict if return_dict is not None else self.config.use_return_dict

        outputs = self.transformer(
            input_ids,
            attention_mask=attention_mask,
            head_mask=head_mask,
            inputs_embeds=inputs_embeds,
            output_attentions=output_attentions,
            output_hidden_states=output_hidden_states,
            return_dict=return_dict,
        )
        sequence_output = outputs[0]

        logits = self.classification_head(sequence_output)

        loss = None
        if labels is not None:
            labels = labels.to(logits.device)
            loss_fct = CrossEntropyLoss()
            loss = loss_fct(logits.view(-1, self.config.num_labels), labels.view(-1))

        if not return_dict:
            output = (logits,) + outputs[1:]
            return ((loss,) + output) if loss is not None else output

        return TokenClassifierOutput(
            loss=loss,
            logits=logits,
            hidden_states=outputs.hidden_states,
            attentions=outputs.attentions,
        )

def process(text, prompt, treshold=0.5):
    """
    Processes text by preparing prompt and adjusting indices.

    Args:
      text (str): The text to process
      prompt (str): The prompt to prepend to the text

    Returns:
      list: A list of dicts with adjusted spans and scores
    """

    # Concatenate text and prompt for full input
    input_ = f"{prompt}\n{text}"

    results = nlp(input_) # Run NLP on full input

    processed_results = []

    prompt_length = len(prompt) # Get prompt length

    for result in results:
        # check whether score is higher than treshold
        if result['score']<treshold:
            continue
        # Adjust indices by subtracting prompt length
        start = result['start'] - prompt_length

        # If indexes belongs to the prompt - continue
        if start<0:
            continue

        end = result['end'] - prompt_length

        # Extract span from original text using adjusted indices
        span = text[start:end]

        # Create processed result dict
        processed_result = {
            'span': span,
            'start': start,
            'end': end,
            'score': result['score']
        }

        processed_results.append(processed_result)

    return processed_results

tokenizer = AutoTokenizer.from_pretrained("knowledgator/UTC-T5-large")
model = T5EncoderForTokenClassification.from_pretrained("knowledgator/UTC-T5-large")

nlp = pipeline("ner", model=model, tokenizer=tokenizer, aggregation_strategy = 'first')
